# 
# 	setup.s		(C) 1991 Linus Torvalds
# 
#  setup.s is responsible for getting the system data from the BIOS,
#  and putting them into the appropriate places in system memory.
#  both setup.s and system has been loaded by the bootblock.
# 
#  This code asks the bios for memory/disk/other parameters, and
#  puts them in a "safe" place: 0x90000-0x901FF, ie where the
#  boot-block used to be. It is then up to the protected mode
#  system to read them from there before the area is overwritten
#  for buffer-blocks.
# 

#  NOTE#  These had better be the same as in bootsect.s# 

INITSEG  = 0x9000	#  we move boot here - out of the way
SYSSEG   = 0x1000	#  system loaded at 0x10000 (65536).
SETUPSEG = 0x9020	#  this is the current segment

#  .globl begtext, begdata, begbss, endtext, enddata, endbss
#  .text
#  begtext:
#  .data
#  begdata:
#  .bss
#  begbss:
#  .text
	.text
	.code16
	.globl _start
# entry start
_start:
	#  操作系统需要知道一些有关计算机硬件的一些基本知识，比如说采用了什么样的显示器，显卡特性？
	#  另外还要检测当前整个系统中有多少块硬盘，以及硬盘相关的一些属性信息。
	#  这些信息需要在进入保护模式之前完成，因为使用了BIOS中断进入保持服务模式之后，BIOS中断将不可用。
	#  ok, the read went well so we get current cursor position and save it for
	#  posterity.
	#  读取光标位置，保存到0x90000处。
	#  相当于read_cursor(uint16_t row_col), *((char *)0x90000) =》 row_col
	#  功能描述：在文本坐标下，读取光标各种信息
	#  入口参数：AH＝03H
	#  BH＝显示页码
	#  出口参数：CH＝光标的起始行
	#  CL＝光标的终止行
	#  DH＝行(Y坐标)
	#  DL＝列(X坐标)
	mov    $INITSEG,%ax		#  mov	ax,#INITSEG	#  this is done in bootsect already, but...
	mov    %ax,%ds		#  mov	ds,ax
	mov    $0x3,%ah		#  mov	ah,#0x03	#  read cursor pos
	xor    %bh,%bh		#  xor	bh,bh
	int    $0x10		#  int	0x10		#  save it in known place, con_init fetches
	mov    %dx,0x0		#  mov	[0],dx		#  it from 0x90000.只保存了行和列
#  Get memory size (extended mem, kB)

	#  读取内存大小：相当于 uint16_t get_mem_size() => 0x90002
	#  功能号: 0x88
	mov    $0x88,%ah		#  mov	ah,#0x88
	int    $0x15			#  int	0x15
	mov    %ax,0x2			#  mov	[2],ax

	#  获取显示相关的参数信息，方便后面在屏幕上显示时使用
	#  不同的显示模式有不同的显示方法，屏幕宽度不同决定了很列显示时需要做特定的处理
	#  Get video-card data:
	#  获取显示页、显示模式、字长列数
	#  功能描述：读取显示器模式
	#  入口参数：AH＝0FH
	#  出口参数：AH＝屏幕字符的列数
	#  AL＝显示模式(参见功能00H中的说明)
	#  BH＝页码
	mov    $0xf,%ah			#  mov	ah,#0x0f
	int    $0x10			#  int	0x10
	mov    %bx,0x4		#  mov	[4],bx		#  bh = display page
	mov    %ax,0x6		#  mov	[6],ax		#  al = video mode, ah = window width

	#  check for EGA/VGA and some config parameters
	#  获取显卡特定：比如黑白、彩色、显存大小等其它数值，后续将使用
	#  功能: al = 12H
	#  bl: 子功能，10H	读取配置信息
	#  bh: 视频状态：00 - 彩色，01 - 单色
	#  bl ：显存大小，00-64Kb, 01=12kb, 02=192kb, 03=256kb
	#  cb：连接器特性 cl：视频开关信息
	mov    $0x12,%ah		#  mov	ah,#0x12
	mov    $0x10,%bl		#  mov	bl,#0x10
	int    $0x10		#  int	0x10
	mov    %ax,0x8		#  mov	[8],ax			
	mov    %bx,10		#  mov	[10],bx
	mov    %cx,12		#  mov	[12],cx

	#  Get hd0 data
	#  从0x104处读取硬盘相关的一些属性信息，一共16字节
	#  相当于memcpy(0x90080, 0x104, 16)
	mov    $0x0,%ax		#  mov	ax,#0x0000
	mov    %ax,%ds		#  mov	ds,ax
	lds    0x104,%si		#  lds	si,[4*0x41]
	mov    $INITSEG,%ax		#  mov	ax,#INITSEG
	mov    %ax,%es			#  mov	es,ax
	mov    $0x80,%di		#  mov	di,#0x0080
	mov    $0x10,%cx		#  mov	cx,#0x10，一共16字节
	rep movsb %ds:(%si),%es:(%di)	#  rep movsb

	#  Get hd1 data
	#  从0x118处读取硬盘相关的一些属性信息
	#  相当于memcpy(0x90090, 0x118, 16)
	mov    $0x0,%ax				# / mov	ax,#0x0000
	mov    %ax,%ds				#  mov	ds,ax
	lds    0x118,%si			#  lds	si,[4*0x46]
	mov    $INITSEG,%ax			#  mov	ax,#INITSEG
	mov    %ax,%es				#  mov	es,ax
	mov    $0x90,%di			#  mov	di,#0x0090
	mov    $0x10,%cx			#  mov	cx,#0x10
	rep movsb %ds:(%si),%es:(%di)	#  rep movsb

	#  Check that there IS a hd1 :-)
	#  查询bios是否磁盘1存在
	mov    $0x1500,%ax			#  mov	ax,#0x01500
	mov    $0x81,%dl			#  mov	dl,#0x81
	int    $0x13				#  int	0x13
	jc	no_disk1
	cmp    $0x3,%ah				#  cmp	ah,#3
	je	is_disk1

	#  不存在清空参数表
	#  相当于memset(0x90090, 0, 16)
no_disk1:
	mov    $INITSEG,%ax			#  mov	ax,#INITSEG
	mov    %ax,%es				#  mov	es,ax
	mov    $0x90,%di			#  mov	di,#0x0090
	mov    $0x10,%cx			#  mov	cx,#0x10
	mov    $0x0,%ax				#  mov	ax,#0x00
	rep stos %al,%es:(%di)		#  rep stosb
is_disk1:

	#  now we want to move to protected mode ...
	#  进入保护模式之后，原来的中断向量表不可用，如果这时候发生中断就导致整个系统跑飞，所以需要关中断。
	cli			#  no interrupts allowed # 

	#  first we move the system to it's rightful place
	#  将system模块移动到0地址处。
	#  相当于memmove(0x0, 0x10000, 0x90000-0x10000)
	#  之所以之前没有直接下载到0地址处，是因为需要使用BIOS中断,而0地址处存放的中断信号表。
	#  现在BIOS中断向量表那边已经不需要了, 因此这一部分就利用起来。为了节省内存。
	mov    $0x0,%ax		#  mov	ax,#0x0000
	cld			#  'direction'=0, movs moves forward
do_move:
	mov    %ax,%es			#  mov	es,ax		#  destination segment
	add    $0x1000,%ax		#  add	ax,#0x1000	#  从0x10000处开始移动
	cmp    $0x9000,%ax		#  cmp	ax,#0x9000	#  地址不超过0x90000，即最大为0x80000的范围，共512KB
	jz		end_move		#  jz	end_move
	mov    %ax,%ds			#  mov	ds,ax		#  source segment
	sub    %di,%di			#  sub	di,di
	sub    %si,%si			#  sub	si,si
	mov    $0x8000,%cx		#  mov 	cx,#0x8000	
	rep movsw %ds:(%si),%es:(%di)	#  rep movsw
	jmp	do_move

#  then we load the segment descriptors

end_move:
	#  重设ds寄存器，因为之前移动system有做修改
	mov    $SETUPSEG,%ax		#  mov	ax,#SETUPSEG	#  right, forgot this at first. didn't work :-)
	mov    %ax,%ds			# / mov	ds,ax

	#  加载保护模式下的中断向量表，相当于 lidtw(&idt_48, limit)
	#  目前这个表为空，其实这个加载不是必须的.因为当前中断关掉的，不会产生中断
	lidtw	idt_48		#  load idt with 0,0

	#  重设GDT表，相当于 lgdtw(&gdt_48, limit)
	#  进入保护模式必须的
	lgdtw	gdt_48		#  load gdt with whatever appropriate

	#  that was painless, now we enable A20
	#  开启A20地址线，从而能访问1MB以上的内存
	#  如果不打开，将不能访问1M以上的内存。
	call	empty_8042
	mov    $0xd1,%al		#  mov	al,#0xD1		#  command write
	out    %al,$0x64		#  out	#0x64,al
	call	empty_8042
	mov    $0xdf,%al		#  mov	al,#0xDF		#  A20 on
	out    %al,$0x60		#  out	#0x60,al
	call	empty_8042

#  well, that went ok, I hope. Now we have to reprogram the interrupts :-(
#  we put them right after the intel-reserved hardware interrupts, at
#  int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
#  messed this up with the original PC, and they haven't been able to
#  rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
#  which is used for the internal hardware interrupts as well. We just
#  have to reprogram the 8259's, and it isn't fun.
	#  配置intel 8259中断控制器
	#  将两块8259配置连接起来，8086模式、普通EOI、非缓冲，目前屏幕所有中断请求
	#  后面在初始化各个硬件对应的模块时，将对相应的中断进行配置，并打开相应的中断请求
	#  发送0x11(初始化命令)到主芯片端口0x20，表示开始进行初始化
	#  下面的配置流程其实没什么分析的，这是芯片的要求，大致了解中断的基本流程即可以了
	mov    $0x11,%al		#  mov	al,#0x11		#  initialization sequence
	out    %al,$0x20		#  out	#0x20,al		#  send it to 8259A-1
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2
	#  发送0x11(初始化命令)到从芯片端口0xA0，表示开始进行初始化
	out    %al,$0xa0		#  out	#0xA0,al		#  and to 8259A-2
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2

	#  发送0x20（表示主芯片中中断的起始序号从0x20开始）命令到主芯片端口0x21端口， 写ICW2
	mov    $0x20,%al		#  mov	al,#0x20		#  start of hardware int's (0x20)
	out    %al,$0x21		#  out	#0x21,al
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2

	#  发送0x28（从芯片的中断起始序号为0x28）命令到从芯片端口0xa1端口， 写ICW2
	mov    $0x28,%al		#  mov	al,#0x28		#  start of hardware int's 2 (0x28)
	out    %al,$0xa1		#  out	#0xA1,al
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2

	#  发送0x4（1 << 2，表示主芯片的IR2上连接有从芯片的INT）命令到主芯片端口0x21端口， 写ICW3
	mov    $0x4,%al			#  mov	al,#0x04		#  8259-1 is master
	out    %al,$0x21		#  out	#0x21,al
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2

	#  发送0x2（表示从芯片的INT连接到主芯片的IRQ2上）命令到从芯片端口0xa1端口， 写ICW3
	mov    $0x2,%al			#  mov	al,#0x02		#  8259-2 is slave
	out    %al,$0xa1		#  out	#0xA1,al
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2

	#  发送0x1（工作模式：8086、普通EOI、非缓存）命令到主芯片端口0x21端口， 写ICW4
	mov    $0x1,%al			#  mov	al,#0x01		#  8086 mode for both
	out    %al,$0x21		#  out	#0x21,al
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2
	#  发送0x1（工作模式：8086、普通EOI、非缓存）命令到从芯片端口0xa1端口， 写ICW4
	out    %al,$0xa1		#  out	#0xA1,al
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2

	#  发送0xff（屏蔽所有中断，即不响应中中断发）命令到主芯片端口0x21端口
	mov    $0xff,%al		#  mov	al,#0xFF		#  mask off all interrupts for now
	out    %al,$0x21		#  out	#0x21,al
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2
	#  发送0xff（屏蔽所有中断，即不响应中中断发）命令到从芯片端口0xa1端口
	out    %al,$0xa1		#  out	#0xA1,al

#  well, that certainly wasn't fun :-(. Hopefully it works, and we don't
#  need no steenking BIOS anyway (except for the initial loading :-).
#  The BIOS-routine wants lots of unnecessary data, and it's less
#  "interesting" anyway. This is how REAL programmers do it.
# 
#  Well, now's the time to actually move into protected mode. To make
#  things as simple as possible, we do no register set-up or anything,
#  we let the gnu-compiled 32-bit programs do that. We just jump to
#  absolute address 0x00000, in 32-bit protected mode.
	#  将cr0的第0位置1，即进入保护模式
	mov    $0x1,%ax			#  mov	ax,#0x0001	#  protected mode (PE) bit
	lmsw   %ax				#  lmsw	ax		#  This is it# 

	#  长跳转进入保护模式，从而加载新的CS段选择子
	#  这里8是GDT第1项，基地址来0.system本身也运行于0地址处，所以偏移量也是0
	ljmp   $0x8,$0x0		#  jmpi	0,8		#  jmp offset 0 of segment 8 (cs)

#  This routine checks that the keyboard command queue is empty
#  No timeout is used - if this hangs there is something wrong with
#  the machine, and we probably couldn't proceed anyway.
empty_8042:
	jmp .+2
	jmp .+2					#  .word	0x00eb,0x00eb		#  jmp .+2, jmp .+2
	in     $0x64,%al		#  in	al,#0x64	#  8042 status port
	test   $0x2,%al			#  test	al,#2		#  is input buffer full?
	jnz	empty_8042	#  yes - loop
	ret

#  GDT表，进入保护模式所必须
gdt:
	.word	0,0,0,0		#  dummy

	#  这个表是临时性的，只是为了进入保护模式使用。所以这个大小不需要设置成
	#  和实际物理内在大小相同的值，可以大一些，也可以小一些，只要能保证代码正常运行即可
	#  代码段， 0 - 8MB
	#  而且以前的内存也比较小
	.word	0x07FF		#  8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		#  base address=0
	.word	0x9A00		#  code read/exec
	.word	0x00C0		#  granularity=4096, 386

	#  数据段， 0 - 8MB
	.word	0x07FF		#  8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		#  base address=0
	.word	0x9200		#  data read/write
	.word	0x00C0		#  granularity=4096, 386

idt_48:
	.word	0			#  idt limit=0，限长
	.word	0,0			#  idt base=0L，超始地址

gdt_48:
	.word	0x800		#  gdt limit=2048, 256 GDT entries，限长
	.word	512+gdt,0x9	#  gdt base = 0X9xxxx，超始地址
	
# .text
# endtext:
# .data
# enddata:
# .bss
# endbss:
